/*
Copyright (c) 2007,2007 Elizeu Nogueira da R. Jr.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.
    * Neither the name of the Elizeu Nogueira da R. Jr. nor the names of its 
      contributors may be used to endorse or promote products derived from this 
      software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


This package is based on HypersonicSQL, originally developed by Thomas Mueller.
and HSQLDB original developed by the HSQL Development Group
*/

package br.com.dreamsource.mobile.jmesql;

import br.com.dreamsource.mobile.jmesql.exceptions.SQLException;
import java.io.*;
import java.util.*;
import br.com.dreamsource.mobile.jmesql.jdbcStatement;

public class jdbcResultSet {
    
    public static int TYPE_FORWARD_ONLY = 1003;
    public static int CONCUR_READ_ONLY = 1007;
    
    public static int FETCH_FORWARD = 1000;
    public static int FETCH_REVERSE = 1001;
    public static int FETCH_UNKNOWN = 1002;    
    
    private Result  rResult;
    private Record  nCurrent;
    private int     iCurrentRow;
    private int     iUpdateCount;
    private boolean bInit;    // false if before first row
    private int     iColumnCount;
    private boolean bWasNull;

    public boolean next() throws SQLException {
	bWasNull = false;    // todo: test if other databases do the same

	if (rResult == null) {
	    return false;    // throw exception ?
	}

	if (bInit == false) {
	    nCurrent = rResult.rRoot;
	    bInit = true;
	    iCurrentRow = 1;
	} else {
	    if (nCurrent == null) {
		return false;
	    }

	    nCurrent = nCurrent.next;
	    iCurrentRow++;
	}

	if (nCurrent == null) {
	    iCurrentRow = 0;

	    return false;
	}

	return true;
    }

    public void close() {
	iUpdateCount = -1;
	rResult = null;
    }

    public boolean wasNull() {
	return bWasNull;
    }

    public int findColumn(String columnName) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace(columnName);
	}

	for (int i = 0; i < iColumnCount; i++) {
	    //if (columnName.equalsIgnoreCase(rResult.sLabel[i])) {
            if (columnName.toUpperCase().equals(rResult.sLabel[i].toUpperCase())) {
		return i + 1;
	    }
	}

	throw Trace.error(Trace.COLUMN_NOT_FOUND);
    }

    public String getString(int column) throws SQLException {
	checkColumn(--column);
	checkAvailable();

	Object o = nCurrent.data[column];

	// use checknull because getColumnInType is not used
	checkNull(o);

	return o == null ? null : o.toString();
    }

    public boolean getBoolean(int column) throws SQLException {
	Object o = getColumnInType(--column, Column.INTEGER);

	return o == null ? false : ((Boolean) o).booleanValue();
    }

    public int getInt(int column) throws SQLException {
	Object o = getColumnInType(--column, Column.INTEGER);

	return o == null ? 0 : ((Integer) o).intValue();
    }

    public float getFloat(int column) throws SQLException {
	Object o = getColumnInType(--column, Column.FLOAT);
	return o == null ? (float) 0.0 : ((Float) o).floatValue();
    }

    public java.util.Date getDate(int column) throws SQLException {
	return (java.util.Date) getColumnInType(--column, Column.DATE);
    }

    public String getString(String columnName) throws SQLException {
	return getString(findColumn(columnName));
    }

    public boolean getBoolean(String columnName) throws SQLException {
	return getBoolean(findColumn(columnName));
    }

    public int getInt(String columnName) throws SQLException {
	return getInt(findColumn(columnName));
    }

    public float getFloat(String columnName) throws SQLException {
	return getFloat(findColumn(columnName));
    }

    public java.util.Date getDate(String columnName) throws SQLException {
	return getDate(findColumn(columnName));
    }

    public void clearWarnings() {
	if (Trace.TRACE) {
	    Trace.trace();
	}
    }

    public String getCursorName() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}
	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public Reader getCharacterStream(String columnName) throws SQLException {
	return getCharacterStream(findColumn(columnName));
    }

    public boolean isBeforeFirst() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	if (bInit == false) {
	    return true;
	}

	return false;
    }

    public boolean isAfterLast() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	if (bInit == false) {
	    return false;
	}

	if (nCurrent == null) {
	    return true;
	}

	return false;
    }

    public boolean isFirst() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return iCurrentRow == 1;
    }

    public boolean isLast() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void beforeFirst() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void afterLast() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public boolean first() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public boolean last() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public int getRow() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return iCurrentRow;
    }

    public boolean absolute(int row) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public boolean relative(int rows) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	if (rows < 0) {
	    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
	}

	while (rows-- > 0) {
	    next();
	}

	return nCurrent != null;
    }

    public boolean previous() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void setFetchDirection(int direction) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace(direction);
	}

	if (direction != FETCH_FORWARD) {
	    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
	}
    }

    public int getFetchDirection() {
	return FETCH_FORWARD;
    }

    public void setFetchSize(int rows) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace(rows);
	}
    }

    public int getFetchSize() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return 1;
    }

    public int getType() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return TYPE_FORWARD_ONLY;
    }

    public int getConcurrency() {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return CONCUR_READ_ONLY;
    }

    public void updateBoolean(int column, boolean x) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void updateInt(int column, int x) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void updateString(int column, String x) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void updateDate(int column, java.util.Date x) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void updateBoolean(String columnName,
			      boolean x) throws SQLException {
	updateBoolean(findColumn(columnName), x);
    }

    public void updateInt(String columnName, int x) throws SQLException {
	updateInt(findColumn(columnName), x);
    }

    public void updateString(String columnName,
			     String x) throws SQLException {
	updateString(findColumn(columnName), x);
    }

    public void updateDate(String columnName,
			   java.util.Date x) throws SQLException {
	updateDate(findColumn(columnName), x);
    }

    public void insertRow() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void updateRow() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void deleteRow() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void refreshRow() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}
    }

    public void cancelRowUpdates() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}
    }

    public void moveToInsertRow() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    public void moveToCurrentRow() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}
    }

    public jdbcStatement getStatement() throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();

	    // todo
	}

	return null;
    }

    public java.util.Date getDate(int column,
				 Calendar cal) throws SQLException {
	return getDate(column);
    }

    public java.util.Date getDate(String columnName,
				 Calendar cal) throws SQLException {
	return getDate(findColumn(columnName));
    }

    public Reader getCharacterStream(int column) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
    }

    jdbcResultSet(Result r) throws SQLException {
	if (r.iMode == Result.UPDATECOUNT) {
	    iUpdateCount = r.iUpdateCount;
	} else if (r.iMode == Result.ERROR) {
	    throw Trace.getError(r.sError);
	} else {
	    iUpdateCount = -1;
	    rResult = r;
	    iColumnCount = r.getColumnCount();
	}

	bWasNull = false;
    }

    int getUpdateCount() {
	return iUpdateCount;
    }

    boolean isResult() {
	return rResult == null ? false : true;
    }

    private void checkColumn(int column) throws SQLException {
	if (column < 0 || column >= iColumnCount) {
	    Trace.error(Trace.COLUMN_NOT_FOUND, column);
	}
    }

    private void checkAvailable() throws SQLException {
	if (rResult == null ||!bInit || nCurrent == null) {
	    Trace.error(Trace.NO_DATA_IS_AVAILABLE);
	}
    }

    private Object getColumnInType(int column, int type) throws SQLException {
	checkColumn(column);
	checkAvailable();

	int    t = rResult.iType[column];
	Object o = nCurrent.data[column];

	checkNull(o);

	// no conversion necessary
	if (type == t) {
	    return o;
	}

	// try to convert
	try {
	    return Column.convertObject(o, type);
	} catch (Exception e) {
	    String s = "type: " + Column.getType(t) + " (" + t
		       + ") expected: " + Column.getType(type) + " value: "
		       + o.toString();

	    throw Trace.error(Trace.WRONG_DATA_TYPE, s);
	}
    }

    private void checkNull(Object o) {
	if (o == null) {
	    bWasNull = true;
	} else {
	    bWasNull = false;
	}
    }

    public int getColumnCount() {
	return iColumnCount;
    }

    public int getColumnDisplaySize(int column) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}
	checkColumn(--column);
	return 0;
    }

    public String getColumnLabel(int column) throws SQLException {
	checkColumn(--column);

	return rResult.sLabel[column];
    }

    public String getColumnName(int column) throws SQLException {
	checkColumn(--column);

	return rResult.sName[column];
    }

    public int getPrecision(int column) {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return 0;
    }

    public String getTableName(int column) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	checkColumn(--column);

	return rResult.sTable[column];
    }


    public String getCatalogName(int column) {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	return "";
    }

    public int getColumnType(int column) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	checkColumn(--column);

	return rResult.iType[column];
    }

    public String getColumnTypeName(int column) throws SQLException {
	if (Trace.TRACE) {
	    Trace.trace();
	}

	checkColumn(--column);

	return Column.getType(rResult.iType[column]);
    }

}
